package cn.nn200433.job.executor.impl;

import cn.hutool.core.collection.CollUtil;
import cn.nn200433.job.annotation.XxlJobPlus;
import cn.nn200433.job.entity.Executor;
import cn.nn200433.job.entity.JobInfo;
import cn.nn200433.job.enums.Trigger;
import cn.nn200433.job.util.XxlRemoteUtil;
import com.xxl.job.core.executor.XxlJobExecutor;
import com.xxl.job.core.glue.GlueFactory;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.job.core.handler.impl.MethodJobHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.env.Environment;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * xxl-job 注解升级版执行器注册（任务自动创建）
 *
 * @author nn200433
 * @date 2024-03-01 04:42:18
 */
@Slf4j
public class XxlJobPlusSpringExecutor extends XxlJobExecutor
        implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {

    private static final String IS_SYNC_MODIFY_REMOTE_KEY = "xxl.job.is-sync-modify-remote";

    @Override
    public void afterSingletonsInstantiated() {
        // init JobHandler存储库（用于方法）
        initJobHandlerMethodRepository(applicationContext);
        // 刷新GlueFactory
        GlueFactory.refreshInstance(1);
        // super start
        try {
            super.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void destroy() {
        super.destroy();
    }

    private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }
        // 从方法初始化作业处理程序
        String[]         beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
        List<XxlJobPlus> xxlJobPlusList      = new ArrayList<XxlJobPlus>();
        for (String beanDefinitionName : beanDefinitionNames) {
            // 获取bean
            Object bean   = null;
            Lazy   onBean = applicationContext.findAnnotationOnBean(beanDefinitionName, Lazy.class);
            if (onBean != null) {
                log.warn("xxl-job 注解扫描, 跳过使用 @Lazy 注解的 Bean {}", beanDefinitionName);
                continue;
            } else {
                bean = applicationContext.getBean(beanDefinitionName);
            }
            filterXxlJob(beanDefinitionName, bean);
            filterXxlJobPlus(beanDefinitionName, bean, xxlJobPlusList);
        }
        addJob2Admin(xxlJobPlusList);
    }

    /**
     * XxlJob 过滤器
     *
     * @param beanDefinitionName bean定义名称
     * @param bean               bean
     * @author nn200433
     */
    private void filterXxlJob(String beanDefinitionName, Object bean) {
        // 过滤方法
        Map<Method, XxlJob> annotatedMethods = null;
        // 参考：：org.springframework.context.event。EventListenerMethodProcessor.processBean
        try {
            annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                                                                new MethodIntrospector.MetadataLookup<XxlJob>() {
                                                                    @Override
                                                                    public XxlJob inspect(Method method) {
                                                                        return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                                                                    }
                                                                });
        } catch (Throwable ex) {
            log.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
        }
        if (null == annotatedMethods || annotatedMethods.isEmpty()) {
            return;
        }
        // 生成和注册方法作业处理程序
        for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
            Method executeMethod = methodXxlJobEntry.getKey();
            XxlJob xxlJob        = methodXxlJobEntry.getValue();
            // 注册
            registJobHandler(xxlJob, bean, executeMethod);
        }
    }

    /**
     * XxlJobPlus 过滤器
     *
     * @param beanDefinitionName bean定义名称
     * @param bean               bean
     * @param xxlJobPlusList     XxlJobPlus注解列表
     * @author nn200433
     */
    private void filterXxlJobPlus(String beanDefinitionName, Object bean, List<XxlJobPlus> xxlJobPlusList) {
        // 过滤方法
        Map<Method, XxlJobPlus> annotatedMethods = null;
        // 参考：：org.springframework.context.event。EventListenerMethodProcessor.processBean
        try {
            annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                                                                new MethodIntrospector.MetadataLookup<XxlJobPlus>() {
                                                                    @Override
                                                                    public XxlJobPlus inspect(Method method) {
                                                                        return AnnotatedElementUtils.findMergedAnnotation(method, XxlJobPlus.class);
                                                                    }
                                                                });
        } catch (Throwable ex) {
            log.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
        }
        if (null == annotatedMethods || annotatedMethods.isEmpty()) {
            return;
        }
        // 生成和注册方法作业处理程序
        for (Map.Entry<Method, XxlJobPlus> methodXxlJobEntry : annotatedMethods.entrySet()) {
            Method     executeMethod = methodXxlJobEntry.getKey();
            XxlJobPlus xxlJobPlus    = methodXxlJobEntry.getValue();
            // 注册
            registerPlusJobHandler(xxlJobPlus, bean, executeMethod);
            // 添加到待远程注册列表
            xxlJobPlusList.add(xxlJobPlus);
        }
    }

    /**
     * 注册作业处理程序
     *
     * @param xxlJob        xxl作业
     * @param bean          豆
     * @param executeMethod execute方法
     * @author nn200433
     */
    private void registerPlusJobHandler(XxlJobPlus xxlJob, Object bean, Method executeMethod) {
        if (xxlJob == null) {
            return;
        }

        String name = xxlJob.value();
        // 生成并简化变量，因为它们稍后会被多次调用
        Class<?> clazz      = bean.getClass();
        String   methodName = executeMethod.getName();
        if (name.trim().length() == 0) {
            throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + clazz + "#" + methodName + "] .");
        }
        if (loadJobHandler(name) != null) {
            throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
        }

        executeMethod.setAccessible(true);

        // 初始化方法 和 销毁方法
        Method initMethod    = null;
        Method destroyMethod = null;
        if (xxlJob.init().trim().length() > 0) {
            try {
                initMethod = clazz.getDeclaredMethod(xxlJob.init());
                initMethod.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] .");
            }
        }
        if (xxlJob.destroy().trim().length() > 0) {
            try {
                destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());
                destroyMethod.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
            }
        }
        // 注册 jobhandler
        registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
    }

    /**
     * 添加任务到调度中心
     *
     * @param xxlJobPlusList xxl作业加列表
     * @author nn200433
     */
    private void addJob2Admin(List<XxlJobPlus> xxlJobPlusList) {
        try {
            final Environment env                = applicationContext.getEnvironment();
            final Boolean     isSyncModifyRemote = env.getProperty(IS_SYNC_MODIFY_REMOTE_KEY, Boolean.class, Boolean.TRUE);
            if (CollUtil.isEmpty(xxlJobPlusList)) {
                return;
            }
            Executor executor = XxlRemoteUtil.preciselyCheck();
            Integer  jobGroup = 0;
            if (null == executor) {
                if (XxlRemoteUtil.autoRegisterExecutor()) {
                    executor = XxlRemoteUtil.preciselyCheck();
                    jobGroup = executor.getId();
                    log.info("自动注册 xxl-job 成功! 执行器 Id 是 {}", executor.getId());
                }
            } else {
                jobGroup = executor.getId();
                final String addressList = executor.getAddressList();
                if (XxlRemoteUtil.updateExecutor(jobGroup, addressList)) {
                    log.info("执行器 {} 地址更新成功...", executor.getTitle());
                }
                ;
            }
            // 获取远程列表
            final Map<String, JobInfo> remoteJobMap = XxlRemoteUtil.getJobInfo(jobGroup, null)
                    .stream()
                    .collect(Collectors.groupingBy(JobInfo::getExecutorHandler, Collectors.collectingAndThen(Collectors.toList(), CollUtil::getFirst)));
            for (final XxlJobPlus xxlJobPlus : xxlJobPlusList) {
                final String  handlerName = xxlJobPlus.value();
                final String  desc        = xxlJobPlus.desc();
                final String  author      = xxlJobPlus.author();
                final String  cron        = xxlJobPlus.cron();
                final Trigger status      = xxlJobPlus.status();
                JobInfo       jobInfo     = remoteJobMap.get(handlerName);
                Integer       jobId       = null;
                if (null == jobInfo) {
                    // 发送注册
                    jobId = XxlRemoteUtil.addJobInfo(JobInfo.builder()
                                                             .jobGroup(jobGroup)
                                                             .jobDesc(desc)
                                                             .author(author)
                                                             .scheduleConf(cron)
                                                             .executorHandler(handlerName)
                                                             .build());
                    log.info("---> {} 任务注册，注册 id 为 {}", handlerName, jobId);
                    if (Trigger.START.equals(status)) {
                        final boolean isStart = XxlRemoteUtil.start(jobId);
                        log.info("---> {} 任务状态修改。启用 {}", handlerName, isStart ? "成功" : "失败");
                    }
                } else {
                    if (isSyncModifyRemote) {
                        // 执行更新
                        jobId = jobInfo.getId();
                        jobInfo.setJobGroup(jobGroup);
                        jobInfo.setJobDesc(desc);
                        jobInfo.setAuthor(author);
                        jobInfo.setScheduleConf(cron);
                        jobInfo.setExecutorHandler(handlerName);
                        XxlRemoteUtil.updateJobInfo(jobInfo);
                        log.info("---> {} 任务更新", handlerName);
                    }
                }
                // 修改状态
                if (null != jobId && isSyncModifyRemote) {
                    switch (status) {
                        case STOP:
                            final boolean isStop = XxlRemoteUtil.stop(jobId);
                            log.info("---> {} 任务状态修改。停止 {}", handlerName, isStop ? "成功" : "失败");
                            break;
                        case START:
                        default:
                            final boolean isStart = XxlRemoteUtil.start(jobId);
                            log.info("---> {} 任务状态修改。启用 {}", handlerName, isStart ? "成功" : "失败");
                    }
                }
            }
        } catch (Exception e) {
            log.error("---> xxl-job 注册任务出错...原因：{}", e.getMessage());
        }
    }

    // ---------------------- applicationContext ----------------------
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        XxlJobPlusSpringExecutor.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

}